#include "music.h"
#include "musicfile.h"
#include "ui_music.h"
#include <QMessageBox>
#include <QSqlError>
#include <QSqlQuery>
#include <QTimer>

Music::Music(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Music)
{
    ui->setupUi(this);
    initUi();
    initPlayer();
    initSQLite();
    initMusicList();
    connectAndSlots();
}
/**
 * @brief 初始化音乐播放器界面的所有UI组件
 * @note 该函数负责设置窗口属性、初始化界面组件、配置音乐列表页面和播放控制按钮
 */
void Music::initUi()
{
    // 关闭标题栏
    this->setWindowFlag(Qt::FramelessWindowHint);
    // 设置窗口背景透明
    this->setAttribute(Qt::WA_TranslucentBackground);
    setWindowIcon(QIcon(":/images/tubiao.png"));
    // 给窗口设置阴影效果
    QGraphicsDropShadowEffect *shadowEffect = new QGraphicsDropShadowEffect(this);
    shadowEffect->setOffset(0, 0);     // 设置阴影偏移
    shadowEffect->setColor(Qt::black); // 设置阴影颜色：黑色
    shadowEffect->setBlurRadius(10);   // 设置阴影的模糊半径
    this->setGraphicsEffect(shadowEffect);

    // 设置BodyLeft中6个btForm的信息
    ui->rec->setIconAndText(":/images/rec.png", "推荐", 0);
    ui->music->setIconAndText(":/images/music.png", "音乐馆", 1);
    ui->audio->setIconAndText(":/images/radio.png", "电台", 2);
    ui->like->setIconAndText(":/images/like.png", "我喜欢", 3);
    ui->local->setIconAndText(":/images/local.png", "本地下载", 4);
    ui->recent->setIconAndText(":/images/recent.png", "最近播放", 5);

    // 设置堆叠窗口当前显示的页面索引为4
    ui->stackedWidget->setCurrentIndex(4);
    // 将当前页面指针设置为本地音乐页面
    cur_page_ = ui->localPage;

    // 使用当前时间作为随机数生成器的种子
    srand(time(nullptr));
    // 初始化推荐音乐框，传入随机生成的图片和类型1
    ui->recMusicBox->initRecBoxUi(randomPiction(), 1);
    // 初始化供应音乐框，传入随机生成的图片和类型2
    ui->supplyMusicBox->initRecBoxUi(randomPiction(), 2);

    // 设置"我喜欢"页面的通用UI，包括标题和背景图片
    ui->likePage->setCommonPageUi("我喜欢", ":/images/ilikebg.png");
    // 设置"本地音乐"页面的通用UI，包括标题和背景图片
    ui->localPage->setCommonPageUi("本地音乐", ":/images/tubiao.png");
    // 设置"最近播放"页面的通用UI，包括标题和背景图片
    ui->recentPage->setCommonPageUi("最近播放", ":/images/recentbg.png");

    // 设置播放按钮的图标和图标大小
    ui->play->setIcon(QIcon(":/images/play3.png"));
    ui->play->setIconSize(QSize(32, 32));
    // 设置播放模式按钮的图标
    ui->playMode->setIcon(QIcon(":/images/shuffle_2.png"));
    // 设置歌词显示按钮的图标和大小
    ui->lrcWord->setIcon(QIcon(":/images/ci.png"));
    ui->lrcWord->setIconSize(QSize(24, 24));

    // 创建音量控制工具对象
    volume_tool_ = new volumeTool(this);

    // 设置音乐封面图片不缩放内容，保持原始比例
    ui->musicCover->setScaledContents(false);

    // 创建歌词页面对象并初始隐藏
    lrc_page_ = new LyricsPage(this);
    lrc_page_->hide();

    // 创建歌词页面的动画效果对象
    lrc_animation_ = new QPropertyAnimation(lrc_page_, "geometry", this);
    // 设置动画持续时间为500毫秒
    lrc_animation_->setDuration(500);
    // 设置动画的起始值（从页面下方滑入）
    lrc_animation_->setStartValue(QRect(10, 10 + lrc_page_->height(), lrc_page_->width(), lrc_page_->height()));
    // 设置动画的结束值（显示在正常位置）
    lrc_animation_->setEndValue(QRect(10, 10, lrc_page_->width(), lrc_page_->height()));
}
/**
 * @brief 连接信号和槽函数，初始化界面组件
 * 
 * 此函数负责设置音乐播放器界面中各个按钮的信号与槽的连接，
 * 包括播放控制按钮、页面切换按钮等。同时初始化推荐音乐盒
 * 和补充音乐盒的界面，并设置页面更新相关的信号槽连接。
 * 
 * @note 此函数在Music类初始化时被调用，用于建立完整的信号槽机制
 */
void Music::connectAndSlots()
{
    // 将 leftBox 按钮的 btClick 信号连接到 on_btClicked 槽函数
    connect(ui->rec, &btForm::btClick, this, &Music::on_btClicked);
    connect(ui->music, &btForm::btClick, this, &Music::on_btClicked);
    connect(ui->audio, &btForm::btClick, this, &Music::on_btClicked);
    connect(ui->like, &btForm::btClick, this, &Music::on_btClicked);
    connect(ui->local, &btForm::btClick, this, &Music::on_btClicked);
    connect(ui->recent, &btForm::btClick, this, &Music::on_btClicked);

    // 初始化推荐音乐盒和补充音乐盒的UI界面
    ui->recMusicBox->initRecBoxUi(randomPiction(), 1);
    ui->supplyMusicBox->initRecBoxUi(randomPiction(), 2);

    // 连接各个页面更新收藏音乐的信号到对应的槽函数
    connect(ui->likePage, &commonPage::updateLikeMusic, this, &Music::updateLikeMusicAndPage);
    connect(ui->localPage, &commonPage::updateLikeMusic, this, &Music::updateLikeMusicAndPage);
    connect(ui->recentPage, &commonPage::updateLikeMusic, this, &Music::updateLikeMusicAndPage);

    // 连接播放按钮的点击信号到音乐播放槽函数
    connect(ui->play, &QPushButton::clicked, this, &Music::onPlayMusic);

    // 连接上一首和下一首按钮的点击信号到对应的槽函数
    connect(ui->playUp, &QPushButton::clicked, this, &Music::onPlayUpClicked);
    connect(ui->playDown, &QPushButton::clicked, this, &Music::onPlayDownClicked);

    // 连接各个页面的播放全部按钮信号到对应的槽函数
    connect(ui->likePage, &commonPage::playAll, this, &Music::onPlayAll);
    connect(ui->localPage, &commonPage::playAll, this, &Music::onPlayAll);
    connect(ui->recentPage, &commonPage::playAll, this, &Music::onPlayAll);

    // 连接likePage页面的playMusicByIndex信号到Music类的playMusicByIndex槽函数
    connect(ui->likePage, &commonPage::playMusicByIndex, this, &Music::playMusicByIndex);
    // 连接localPage页面的playMusicByIndex信号到Music类的playMusicByIndex槽函数
    connect(ui->localPage, &commonPage::playMusicByIndex, this, &Music::playMusicByIndex);
    // 连接recentPage页面的playMusicByIndex信号到Music类的playMusicByIndex槽函数
    connect(ui->recentPage, &commonPage::playMusicByIndex, this, &Music::playMusicByIndex);

    // 连接volumeTool的setSilence信号到Music类的setPlayerVolume槽函数
    connect(volume_tool_, &volumeTool::setSilence, this, &Music::setPlayerVolume);

    // 连接volumeTool的setMusicVolume信号到Music类的setPlayerVolume槽函数
    connect(volume_tool_, &volumeTool::setMusicVolume, this, &Music::setPlayerVolume);

    // 连接musicSlider的setMusicSliderPosition信号到Music类的onMusicSliderChanged槽函数
    connect(ui->processBar, &musicSlider::setMusicSliderPosition, this, &Music::onMusicSliderChanged);

    // 连接lrcWord按钮的clicked信号到Music类的onLrcWordClicked槽函数
    connect(ui->lrcWord, &QPushButton::clicked, this, &Music::onLrcWordClicked);
}
/**
 * @brief 生成一个包含随机图片路径和文本的JSON数组
 *
 * 该函数首先定义一个包含多个图片文件名的QVector，然后使用随机数生成器打乱这些文件名的顺序。
 * 接着，函数创建一个QJsonArray，并为每个文件名生成一个QJsonObject，其中包含图片的路径和对应的文本。
 * 最后，函数返回这个JSON数组。
 * 
 * @return QJsonArray 包含随机图片路径和文本的JSON数组。
 */
QJsonArray Music::randomPiction()
{
    // 创建一个QString类型的向量，用于存储图片名称
    QVector<QString> vecImageName;
    // 向向量中添加图片名称
    vecImageName << "001.png" << "003.png" << "004.png" << "005.png" << "006.png" << "007.png"
                 << "008.png" << "009.png" << "010.png" << "011.png" << "012.png" << "013.png"
                 << "014.png" << "015.png" << "016.png" << "017.png" << "018.png" << "019.png"
                 << "020.png" << "021.png" << "022.png" << "023.png" << "024.png" << "025.png"
                 << "026.png" << "027.png" << "028.png" << "029.png" << "030.png" << "031.png"
                 << "032.png" << "033.png" << "034.png" << "035.png" << "036.png" << "037.png"
                 << "038.png" << "039.png" << "040.png";
    // 创建一个随机数生成器
    std::random_device rd;
    std::mt19937 g(rd());
    // 对向量中的元素进行随机排序
    std::shuffle(vecImageName.begin(), vecImageName.end(), g);

    // 创建一个QJsonArray对象，用于存储图片信息
    QJsonArray objArray;
    // 遍历向量中的元素
    for (qint64 i = 0; i < vecImageName.size(); i++)
    {
        // 创建一个QJsonObject对象，用于存储图片信息
        QJsonObject obj;
        // 将图片路径添加到QJsonObject对象中
        obj.insert("path", ":/images/rec/" + vecImageName[i]);

        // 创建一个字符串，用于存储图片文本
        QString strText = QString("推荐-%1").arg(i, 3, 10, QChar('0'));
        // 将图片文本添加到QJsonObject对象中
        obj.insert("text", strText);
        // 将QJsonObject对象添加到QJsonArray对象中
        objArray.append(obj);
    }
    // 返回QJsonArray对象
    return objArray;
}
/**
 * @brief 根据当前页面更新按钮动画状态
 * @details 该函数会查找所有btForm类型的子对象，根据当前页面的索引，
 *          设置对应按钮的动画状态为显示，其他按钮的动画状态为隐藏
 * @note 该函数是const成员函数，不会修改对象状态
 */
void Music::updateBtFromAnimal() const
{
    // 获取当前页面在堆栈窗口中的索引位置
    const int index = ui->stackedWidget->indexOf(cur_page_);
    // 检查页面是否存在，如果不存在则输出调试信息并返回
    if (index == -1)
    {
        Debug << "该页面不存在";
        return;
    }
    // 查找所有btForm类型的子控件
    QList<btForm *> bt_froms = this->findChildren<btForm *>();
    // 遍历所有btForm控件
    for (const auto &bt_from : bt_froms)
    {
        // 如果当前控件的页面ID与索引匹配，则显示动画
        if (bt_from->getPageId() == index)
            bt_from->showAnimal(true);
        else
            // 否则隐藏动画
            bt_from->showAnimal(false);
    }
}
/**
 * @brief 析构函数，负责释放Music对象占用的资源
 * @details 该函数会在Music对象被销毁时自动调用，主要用于释放动态分配的内存
 *          目前主要是释放ui指针指向的资源
 * @note 该函数会自动被调用，不需要手动调用
 */
Music::~Music()
{
    delete ui;
}

// 播放器设置
/**
 * @brief 初始化音乐播放器
 * @details 该函数负责创建和配置音乐播放器的各个组件，包括：
 *          - 创建QMediaPlayer实例
 *          - 设置音频输出设备
 *          - 创建播放列表并设置为循环模式
 *          - 绑定播放器和播放列表
 *          - 设置初始音量为0.2
 *          - 连接各种信号和槽，包括播放状态变化、错误处理、播放模式切换等
 * @note 此函数会创建新的QMediaPlayer、QAudioOutput和QMediaPlaylist对象
 */
void Music::initPlayer()
{
    // 创建媒体播放器对象，this表示父对象，用于内存管理
    player_ = new QMediaPlayer(this);
    // 创建音频输出对象，this表示父对象，用于内存管理
    QAudioOutput *audio_out_put = new QAudioOutput(this);
    // 设置媒体播放器的音频输出
    player_->setAudioOutput(audio_out_put);
    // 创建播放列表对象，this表示父对象，用于内存管理
    play_list_ = new QMediaPlaylist(this);
    // 设置播放列表的播放模式为循环播放
    play_list_->setPlaybackMode(QMediaPlaylist::Loop);
    // 将播放列表与播放器绑定
    play_list_->bindPlayer(player_);
    // 设置音频输出音量为0.2（范围0.0-1.0）
    audio_out_put->setVolume(0.2);
    // 连接播放器状态变化信号到对应的槽函数
    connect(player_, &QMediaPlayer::playbackStateChanged, this, &Music::onPlayStateChanged);
    // 连接播放器错误信号，使用Lambda表达式处理错误信息
    connect(player_, &QMediaPlayer::errorOccurred, this, [this](const QMediaPlayer::Error error) {
        // 输出错误信息和错误描述
        Debug << "播放错误:" << error << player_->errorString();
    });
    // 连接播放模式按钮点击信号到对应的槽函数
    connect(ui->playMode, &QPushButton::clicked, this, &Music::onPlaybackModeClicked);
    // 连接播放列表播放模式变化信号到对应的槽函数
    connect(play_list_, &QMediaPlaylist::playbackModeChanged, this, &Music::onPlaybackModeChanged);

    // 连接播放列表的当前索引变化信号到对应的槽函数
    connect(play_list_, &QMediaPlaylist::currentIndexChanged, this, &Music::onCurrentIndexChanged);

    // 连接播放器的总时长变化信号到对应的槽函数
    connect(player_, &QMediaPlayer::durationChanged, this, &Music::onDurationChanged);
    // 连接播放器的当前播放位置变化信号到对应的槽函数
    connect(player_, &QMediaPlayer::positionChanged, this, &Music::onPositionChanged);

    // 连接播放器的媒体源变化信号到Lambda表达式
    connect(player_, &QMediaPlayer::sourceChanged, this, [this](const QUrl &source) {
        Q_UNUSED(source); // 标记source参数为未使用，避免编译器警告
        // 使用单次定时器，延迟100毫秒后执行handleMetaData函数
        QTimer::singleShot(100, this, &Music::handleMetaData);
    });
}
/**
 * @brief 初始化SQLite数据库
 * @details 该函数负责创建和初始化SQLite数据库，包括：
 *          - 创建或打开名为"Music.db"的数据库文件
 *          - 创建存储音乐信息的musicInfo表（如果表不存在）
 *          - 表包含音乐ID、名称、歌手、专辑、时长、URL、是否喜欢、是否历史播放等字段
 * @note 数据库使用QSQLITE驱动，表结构包含音乐的所有元数据信息
 */
void Music::initSQLite()
{
    // 创建并连接SQLite数据库
    sqlite_ = QSqlDatabase::addDatabase("QSQLITE"); // 添加SQLite数据库驱动
    sqlite_.setDatabaseName("Music.db");            // 设置数据库名称为Music.db
    // 尝试打开数据库，如果失败则显示错误信息并返回
    if (!sqlite_.open())
    {
        QMessageBox::critical(this, "打开MusicDB失败", sqlite_.lastError().text());
        return;
    }
    // 输出连接成功地调试信息
    Debug << "SQLite连接成功，并创建[Music.db]数据库!!!";
    // 定义创建musicInfo表的SQL语句
    // 表包含字段：id(主键自增), musicId(唯一), musicName, musicSinger,
    // albumName, duration, musicUrl, isLike, isHistory
    const QString sql = (R"(CREATE TABLE IF NOT EXISTS musicInfo(
                        id INTEGER PRIMARY KEY AUTOINCREMENT,
                        musicId varchar(200) UNIQUE,
                        musicName varchar(50),
                        musicSinger varchar(50),
                        albumName varchar(50),
                        duration BIGINT,
                        musicUrl varchar(256),
                        isLike INTEGER,
                        isHistory INTEGER))");
    // 创建并执行SQL查询
    QSqlQuery query;
    if (!query.exec(sql))
    {
        QMessageBox::critical(this, "创建数据库表失败", query.lastError().text());
        return;
    }
    // 输出创建表成功地调试信息
    Debug << "创建[musicInfo]表成功!!!";
}
/**
 * @brief 初始化音乐列表
 * @details 该函数负责从数据库加载音乐信息并刷新各个页面的显示：
 *          - 从数据库读取所有音乐信息
 *          - 设置喜欢页面、本地页面和最近播放页面的音乐列表类型
 *          - 刷新各个页面的音乐列表显示
 * @note 此函数在程序启动时调用，确保所有页面显示最新的音乐信息
 */
void Music::initMusicList()
{
    // 从数据库读取音乐列表数据
    music_list_.readFromDB();

    // 设置"喜欢"页面的音乐列表类型并刷新显示
    ui->likePage->setMusicListType(pageType::LIKE_PAGE);
    ui->likePage->reFresh(music_list_);

    // 设置"本地"页面的音乐列表类型并刷新显示
    ui->localPage->setMusicListType(pageType::LOCAL_PAGE);
    ui->localPage->reFresh(music_list_);

    // 设置"最近"页面的音乐列表类型并刷新显示
    ui->recentPage->setMusicListType(pageType::HISTORY_PAGE);
    ui->recentPage->reFresh(music_list_);
}

// 槽函数逻辑编写(public)
/**
 * @brief 处理按钮点击事件，切换到对应的页面并更新按钮背景颜色
 *
 * @param pageId 要切换到的页面ID
 */
void Music::on_btClicked(const int pageId) const
{
    // 根据点击的按钮，切换到对应的页面
    // ui->stackedWidget->setCurrentIndex(pageId);
    // Debug << "切换页面" << pageId;

    // 1.获取当前页面所有btFrom按钮类型的对象
    QList<btForm *> btList = this->findChildren<btForm *>();
    // 2.遍历所有对象, 如果不是当前id的按钮,则把之前设置的背景颜色清除掉
    foreach (auto bForm, btList)
        if (bForm->getPageId() != pageId)
            bForm->backGroundClear();
    // 3.设置当前栈空间显示页面
    ui->stackedWidget->setCurrentIndex(pageId);
}
/**
 * @brief 音量按钮点击事件处理函数
 *
 * 当用户点击音量按钮时，该函数会被调用。它负责计算音量工具窗口的位置并显示它。
 * 音量工具窗口会显示在音量按钮的上方位置。
 *
 * 位置计算方法：
 * 1. 获取音量按钮在全局坐标系中的位置
 * 2. 根据音量工具窗口的尺寸调整显示位置，使其居中对齐在音量按钮上方
 * 3. 添加偏移量微调位置
 * 4. 将音量工具窗口移动到计算出的位置并显示
 */
void Music::on_volume_clicked() const
{
    // 获取音量控件在全局坐标系中的位置
    QPoint point = ui->volume->mapToGlobal(QPoint(0, 0));
    // 计算音量工具提示的左上角坐标，使其居中显示在音量控件上方
    QPoint volumeLeftTop = point - QPoint(volume_tool_->width() / 2, volume_tool_->height());
    // 调整x坐标，向右偏移110像素，使其更美观
    volumeLeftTop.setX(volumeLeftTop.x() + 110);
    // 调整y坐标，向下偏移90像素，确保工具提示不会紧贴音量控件
    volumeLeftTop.setY(volumeLeftTop.y() + 90);
    // 移动音量工具提示到计算好的位置
    volume_tool_->move(volumeLeftTop);
    // 显示音量工具提示
    volume_tool_->show();
}
/**
 * @brief 添加本地音乐按钮点击事件处理函数
 *
 * 当用户点击添加本地音乐按钮时，该函数会被调用。它会打开一个文件选择对话框，
 * 允许用户选择本地音乐文件并将其添加到音乐列表中。
 *
 * 函数执行以下操作：
 * 1. 创建文件选择对话框，设置标题为"添加本地音乐"
 * 2. 设置默认目录为项目根目录下的music文件夹
 * 3. 配置对话框支持选择多个现有文件
 * 4. 设置MIME类型过滤器
 * 5. 如果用户确认选择文件，则：
 *    - 获取选中的文件URL列表
 *    - 将这些音乐文件添加到音乐列表中
 *    - 切换到本地音乐页面
 *    - 刷新本地页面的显示内容
 *    - 将音乐添加到播放列表中
 */
void Music::on_addLocal_clicked()
{
    // 创建一个文件对话框对象，父窗口为当前窗口
    QFileDialog fileDialog(this);
    // 设置文件对话框的窗口标题为"添加本地音乐"
    fileDialog.setWindowTitle("添加本地音乐");
    // 获取当前目录
    QDir dir(QDir::current());
    // 返回上一级目录
    dir.cdUp();
    // 获取项目路径，设置为上一级目录下的music文件夹
    QString projectPath = dir.path();
    projectPath += "/music";
    // 输出项目路径、当前文件名和行号，用于调试
    // Debug << projectPath << " " << __FILE__ << " " << __LINE__;
    Debug << projectPath;
    // 设置文件对话框的默认目录为项目路径
    fileDialog.setDirectory(projectPath);
    // 一次打开多个文件
    fileDialog.setFileMode(QFileDialog::ExistingFiles);

    // 通过文件后缀过滤
    // fileDialog.setNameFilter("代码文件(*.h *.cpp)");
    // fileDialog.setNameFilter("图片文件(*.png *.jpg)");

    // 使用MIME过滤器
    QStringList mimeTypeFilers;
    mimeTypeFilers << "application/octet-stream"; // All(*)
    fileDialog.setMimeTypeFilters(mimeTypeFilers);

    if (QDialog::Accepted == fileDialog.exec())
    {
        // 测试
        // Debug << "打开";
        // 获取选中的文件
        QList<QUrl> fileUrls = fileDialog.selectedUrls();
        // 通过URL添加音乐到音乐列表
        music_list_.addMusicByUrl(fileUrls);
        // 切换界面到第5个页面（索引从0开始，所以是第5个）
        ui->stackedWidget->setCurrentIndex(4);
        // 刷新本地页面显示的音乐列表，并将音乐添加到播放列表中
        ui->localPage->reFresh(music_list_);
        ui->localPage->addMusicToPlayer(music_list_, play_list_);
    }
}
/**
 * @brief 处理音乐播放按钮点击事件
 * @details 该函数响应用户点击播放按钮的操作，根据当前播放器状态执行相应的播放控制：
 *          - 如果当前正在播放，则暂停播放
 *          - 如果当前已暂停，则继续播放
 *          - 如果当前已停止，则开始播放
 *          - 自动从音乐列表或播放列表中获取音乐作为播放源
 *          - 更新最近播放记录
 *          - 处理音乐元数据
 */
void Music::onPlayMusic()
{
    // 打印播放按钮点击的调试信息
    // Debug << "播放按钮点击";
    // 从音乐列表中获取第一首音乐的URL
    // QUrl musicUrl = music_list_.begin()->getMusicUrl();
    // 打印获取到的音乐URL
    // Debug << "音乐URL:" << musicUrl;
    // 检查当前播放器的源是否与音乐URL相同
    // if (player_->source() != musicUrl)
    // {
    //     // 如果不同，设置新的音乐资源
    //     Debug << "设置新的音乐资源";
    //     player_->setSource(musicUrl);
    // }
    // 检查播放器源是否为空且播放列表中有媒体项
    if (player_->source().isEmpty() && play_list_->mediaCount() > 0)
    {
        // 获取当前媒体URL
        QUrl music_url = play_list_->currentMedia();
        // 检查URL是否有效
        if (!music_url.isEmpty())
        {
            // 输出调试信息：设置播放列表当前音乐资源
            Debug << "设置播放列表当前音乐资源";
            // 设置播放器源为当前媒体URL
            player_->setSource(music_url);
        }
        else
        {
            // 输出调试信息：播放列表为空，从音乐列表获取第一首音乐
            Debug << "播放列表为空，从音乐列表获取第一首音乐";
            // 从音乐列表获取第一首音乐的URL
            music_url = music_list_.begin()->getMusicUrl();
            // 输出调试信息：显示音乐URL
            Debug << "音乐URL:" << music_url;
            // 设置播放器源为获取的音乐URL
            player_->setSource(music_url);
        }
    }
    // 检查播放器源是否不为空
    else if (!player_->source().isEmpty())
    {
        // 输出调试信息：显示当前音乐URL
        Debug << "当前音乐URL:" << player_->source();
    }
    // 处理没有音乐源的情况
    else
    {
        // 输出调试信息：没有音乐源，从音乐列表获取第一首音乐
        Debug << "没有音乐源，从音乐列表获取第一首音乐";
        // 从音乐列表获取第一首音乐的URL
        QUrl music_url = music_list_.begin()->getMusicUrl();
        // 输出调试信息：显示音乐URL
        Debug << "音乐URL:" << music_url;
        // 设置播放器源为获取的音乐URL
        player_->setSource(music_url);
    }

    // 更新最近播放记录
    // 检查播放器是否有音乐源
    if (!player_->source().isEmpty())
    {
        // 遍历音乐列表
        for (auto it = music_list_.begin(); it != music_list_.end(); ++it)
        {
            // 查找当前播放的音乐在列表中的位置
            if (it->getMusicUrl() == player_->source())
            {
                // 只有当音乐不是已经在历史记录中时才更新
                if (!it->getIsHistory())
                {
                    // 将该音乐标记为已播放历史
                    it->setIsHistory(true);
                    // 刷新最近播放页面
                    ui->recentPage->reFresh(music_list_);
                }
                // 找到匹配项后退出循环
                break;
            }
        }
    }

    // 根据当前播放状态执行相应的播放控制操作
    switch (player_->playbackState())
    {
    case QMediaPlayer::PlayingState: // 如果当前状态为正在播放
        Debug << "当前正在播放，将暂停";
        player_->pause();
        break;
    case QMediaPlayer::PausedState: // 如果当前状态为已暂停
        Debug << "当前已暂停，将继续播放";
        player_->play();
        break;
    case QMediaPlayer::StoppedState: // 如果当前状态为已停止
        Debug << "当前已停止，将开始播放";
        // 这里是关键：应该调用play()而不是stop()
        player_->play();
        break;
    default: // 其他未知状态
        Debug << "未知状态，将开始播放";
        player_->play();
        break;
    }

    // 检查并报告播放过程中可能发生的错误
    if (player_->error() != QMediaPlayer::NoError)
        Debug << "播放器错误:" << player_->errorString();

    Debug << "操作后状态:" << player_->playbackState();

    // 处理元数据
    if (!player_->source().isEmpty())
    {
        handleMetaData();
    }
}
/**
 * @brief 响应媒体播放器播放状态变化的槽函数
 *
 * 根据播放器的不同状态(播放中、已暂停、已停止)更新播放按钮的图标和日志输出
 *
 * @param state QMediaPlayer的播放状态枚举值
 */
void Music::onPlayStateChanged(const QMediaPlayer::PlaybackState state)
{
    // 打印播放状态变更信息
    Debug << "播放状态变更:" << state;
    // 输出当前播放的音乐名称
    if (!player_->source().isEmpty())
    {
        for (auto it = music_list_.begin(); it != music_list_.end(); ++it)
        {
            if (it->getMusicUrl() == player_->source())
            {
                Debug << "当前播放音乐:" << it->getMusicName();
                break;
            }
        }
    }
    // 根据不同的播放状态执行相应的操作
    switch (state)
    {
        // 当状态为播放中时
    case QMediaPlayer::PlayingState:
        Debug << "播放中";
        ui->play->setIcon(QIcon(":/images/play_on.png")); // 设置播放按钮图标为播放中状态
        break;
        // 当状态为暂停时
    case QMediaPlayer::PausedState:
        Debug << "已暂停";
        ui->play->setIcon(QIcon(":/images/play3.png")); // 设置播放按钮图标为暂停状态
        break;
        // 当状态为停止时
    case QMediaPlayer::StoppedState:
        Debug << "已停止";
        ui->play->setIcon(QIcon(":/images/play3.png")); // 设置播放按钮图标为停止状态
        break;
    }
}
/**
 * @brief 播放列表上一个按钮点击处理函数
 *
 * 当用户点击"上一个"按钮时调用此函数，用于切换到播放列表中的上一首歌曲。
 * 该函数是Music类的成员函数，被声明为const类型，表明它不会修改类的成员变量。
 */
void Music::onPlayUpClicked() const
{
    play_list_->previous(); // 调用播放列表的previous()方法，切换到上一首歌曲
    if (player_->playbackState() != QMediaPlayer::PlayingState)
        player_->play();
}
/**
 * @brief 播放列表中切换到下一首歌曲
 *
 * 当用户点击播放/下一首按钮时调用此函数
 * 该函数会调用播放列表的next()方法，切换到下一首歌曲
 *
 * @note 此函数是const成员函数，保证不会修改类的成员变量
 */
void Music::onPlayDownClicked() const
{
    // // 调用播放列表对象的next()方法，切换到下一首歌曲
    play_list_->next();
    if (player_->playbackState() != QMediaPlayer::PlayingState)
        player_->play();
    // Debug << "尝试切换到下一首歌曲";
    // Debug << "当前播放列表大小:" << play_list_->mediaCount();
    // Debug << "当前播放索引:" << play_list_->currentIndex();
    //
    // if (play_list_->mediaCount() > 0)
    // {
    //     play_list_->next();
    //     Debug << "切换后播放索引:" << play_list_->currentIndex();
    // }
    // else
    // {
    //     Debug << "播放列表为空，无法切换到下一首";
    // }
}
/**
 * @brief 处理播放模式切换按钮点击事件
 *
 * 根据当前播放模式切换到下一个播放模式:
 * Loop(列表循环) -> Random(随机播放)
 * Random(随机播放) -> CurrentItemInLoop(单曲循环)
 * CurrentItemInLoop(单曲循环) -> Loop(列表循环)
 *
 * @note 此函数没有参数
 * @note 此函数没有返回值
 */
void Music::onPlaybackModeClicked() const
{
    // 检查当前播放模式并切换到下一个模式
    if (play_list_->playbackMode() == QMediaPlaylist::Loop)
    {
        ui->playMode->setToolTip("随机播放");
        play_list_->setPlaybackMode(QMediaPlaylist::Random);
    }
    else if (play_list_->playbackMode() == QMediaPlaylist::Random)
    {
        ui->playMode->setToolTip("单曲播放");
        play_list_->setPlaybackMode(QMediaPlaylist::CurrentItemInLoop);
    }
    else if (play_list_->playbackMode() == QMediaPlaylist::CurrentItemInLoop)
    {
        ui->playMode->setToolTip("列表循环");
        play_list_->setPlaybackMode(QMediaPlaylist::Loop);
    }
    else
    {
        Debug << "播放模式错误";
    }
}
/**
 * @brief 处理播放模式变更事件，根据播放模式设置对应的图标
 * @param playback_mode 播放模式，QMediaPlaylist::PlaybackMode枚举值
 *        - Loop: 列表循环
 *        - Random: 随机播放
 *        - CurrentItemInLoop: 单曲循环
 *
 * 该函数根据传入的播放模式参数，设置播放模式按钮对应的图标：
 * - 列表循环模式使用list_play.png图标
 * - 随机播放模式使用shuffle_2.png图标
 * - 单曲循环模式使用single_play.png图标
 * 如果是不支持的模式，则输出调试信息"暂不支持改模式"
 */
void Music::onPlaybackModeChanged(const QMediaPlaylist::PlaybackMode playback_mode) const
{
    // 根据播放模式设置对应的图标
    if (playback_mode == QMediaPlaylist::Loop)                    // 如果是列表循环模式
        ui->playMode->setIcon(QIcon(":/images/list_play.png"));   // 设置为列表循环图标
    else if (playback_mode == QMediaPlaylist::Random)             // 如果是随机播放模式
        ui->playMode->setIcon(QIcon(":/images/shuffle_2.png"));   // 设置为随机播放图标
    else if (playback_mode == QMediaPlaylist::CurrentItemInLoop)  // 如果是单曲循环模式
        ui->playMode->setIcon(QIcon(":/images/single_play.png")); // 设置为单曲循环图标
    else
        Debug << "暂不支持改模式"; // 其他不支持的模式输出调试信息
}
/**
 * @brief Music类的播放所有音乐函数
 * @param page_type 指定页面的类型，可以是喜欢页面、本地页面或历史页面
 */
void Music::onPlayAll(const pageType page_type)
{
    commonPage *page = nullptr; // 初始化页面指针为空
    // 根据页面类型选择对应的页面
    switch (page_type)
    {
    case pageType::LIKE_PAGE: // 如果是喜欢页面
        page = ui->likePage;  // 设置页面为喜欢页面
        break;
    case pageType::LOCAL_PAGE: // 如果是本地页面
        page = ui->localPage;  // 设置页面为本地页面
        break;
    case pageType::HISTORY_PAGE: // 如果是历史页面
        page = ui->recentPage;   // 设置页面为最近播放页面
        break;
    default:
        Debug << "拓展"; // 其他情况输出"拓展"
    }

    // 调用播放函数，播放指定页面的所有音乐，从索引0开始
    playAllOfCommonPage(page, 0);
}
/**
 * 播放指定公共页面的所有音乐
 * @param page 公共页面对象指针，包含音乐列表信息
 * @param index 指定播放的音乐在播放列表中的索引位置
 */
void Music::playAllOfCommonPage(commonPage *page, const int index)
{
    cur_page_ = page;
    updateBtFromAnimal();
    // 检查参数有效性，如果任一指针为空则直接返回
    if (!page || !play_list_ || !player_)
        return;
    play_list_->clear();                             // 清空当前播放列表
    page->addMusicToPlayer(music_list_, play_list_); // 将公共页面的音乐添加到播放列表中
    play_list_->setCurrentIndex(index);              // 设置当前播放的音乐索引
    player_->play();                                 // 开始播放音乐
}
/**
 * @brief 根据指定页面和索引播放音乐
 * @param page 指定的公共页面指针
 * @param index 要播放的音乐在页面中的索引
 */
void Music::playMusicByIndex(commonPage *page, const int index)
{
    playAllOfCommonPage(page, index);
}
/**
 * @brief 处理播放列表当前索引改变事件
 * @param index 新的当前索引
 *
 * 当播放列表的当前索引发生变化时，该函数会更新当前索引，
 * 将对应音乐标记为已播放历史，并刷新最近播放页面
 */
void Music::onCurrentIndexChanged(const int index)
{
    // 设置当前播放索引
    current_index_ = index;
    // 根据当前索引获取音乐ID
    const QString music_id = cur_page_->getMusicIdByIndex(index);
    // 根据音乐ID在音乐列表中查找对应的迭代器
    const auto it = music_list_.findMusicByMusicId(music_id);
    // 检查是否找到对应的音乐
    if (it != music_list_.end())
    {
        // 只有当音乐不是已经在历史记录中时才更新
        if (!it->getIsHistory())
        {
            // 将音乐标记为已播放（历史记录）
            it->setIsHistory(true);
            // 更新数据库，将音乐信息插入到历史记录表中
            it->insertMusicToDB();
            // 刷新最近播放页面，显示最新的历史记录
            ui->recentPage->reFresh(music_list_);
        }
    }
}
/**
 * @brief 设置播放器音量
 * @param volume 音量值
 */
void Music::setPlayerVolume(int volume) const
{
    player_->audioOutput()->setVolume(volume);
}
/**
 * @brief 处理音乐总时长变化事件
 * @param duration 音乐总时长（毫秒）
 *
 * 当音乐总时长变化时，更新界面上显示的总时间
 */
void Music::onDurationChanged(const qint64 duration) const
{
    // 设置总时间的显示文本，格式为"分:秒"
    // 使用QString的arg方法进行格式化
    ui->totalTime->setText(
        QString("%1:%2").arg(duration / 1000 / 60, 2, 10, QChar('0')).arg(duration / 1000 % 60, 2, 10, QChar('0')));
}
/**
 * @brief 处理音乐播放位置变化事件
 * @param position 当前播放位置（毫秒）
 *
 * 当音乐播放位置变化时，更新界面上显示的当前时间和进度条位置
 */
void Music::onPositionChanged(const qint64 position) const
{
    // 设置当前时间显示，格式为"分:秒"，不足两位补零
    // position为当前播放位置(毫秒)，转换为秒后计算分钟和秒数
    ui->currentTime->setText(
        QString("%1:%2").arg(position / 1000 / 60, 2, 10, QChar('0')).arg(position / 1000 % 60, 2, 10, QChar('0')));
    // 设置进度条的进度值
    // position为当前播放位置(毫秒)，total_time_为总时长(毫秒)，计算进度比例
    ui->processBar->setStep(position / static_cast<float>(total_time_));
    // 如果播放列表中有当前选中的歌曲，则显示对应的歌词
    // position为当前播放位置(毫秒)，用于同步显示歌词
    if (play_list_->currentIndex() >= 0)
        lrc_page_->showLrcWord(position);
}
/**
 * @brief 处理音乐进度条位置变化事件
 * @param ration 进度条位置比例（0.0-1.0）
 *
 * 当用户拖动进度条时，根据比例设置音乐播放位置
 */
void Music::onMusicSliderChanged(const float ration) const
{
    // 计算播放时长，将总时间乘以比例 ration
    const qint64 duration = total_time_ * ration;
    // 设置当前播放时间显示
    // 格式为 "分:秒"，两位数显示，不足两位前面补0
    ui->currentTime->setText(
        QString("%1:%2").arg(duration / 1000 / 60, 2, 10, QChar('0')).arg(duration / 1000 % 60, 2, 10, QChar('0')));
    player_->setPosition(duration);
}
/**
 * @brief 处理歌词按钮点击事件
 *
 * 当用户点击歌词按钮时，显示歌词页面并启动动画
 */
void Music::onLrcWordClicked() const
{
    // 显示LRC(歌词)页面
    lrc_page_->show();
    // 启动LRC动画效果
    lrc_animation_->start();
}

// 槽函数逻辑编写(private)
/**
 * @brief 退出按钮点击事件处理函数
 *
 * 当用户点击退出按钮时，此函数会被调用，用于关闭音乐播放器窗口
 */
void Music::on_quit_clicked()
{
    // 将音乐列表写入数据库
    music_list_.writeToDB();
    // 关闭SQLite数据库连接
    sqlite_.close();
    // 调用窗口的close()函数关闭当前窗口
    close();
}
/**
 * @brief 更新音乐的喜欢状态并刷新相关页面
 *
 * 当音乐的喜欢状态发生变化时，该函数负责更新音乐列表中对应音乐的喜欢状态，
 * 并刷新所有相关页面（喜欢页面、本地页面和最近播放页面）的显示内容。
 *
 * @param is_like 音乐的新喜欢状态，true表示喜欢，false表示不喜欢
 * @param music_id 需要更新的音乐的唯一标识符
 */
void Music::updateLikeMusicAndPage(const bool is_like, const QString &music_id)
{
    // 根据音乐ID查找音乐列表中的音乐
    const auto it = music_list_.findMusicByMusicId(music_id);
    if (it != music_list_.end())          // 如果找到该音乐
        it->setIsLike(is_like);           // 设置该音乐的是否喜欢状态
    ui->likePage->reFresh(music_list_);   // 刷新喜欢页面
    ui->localPage->reFresh(music_list_);  // 刷新本地音乐页面
    ui->recentPage->reFresh(music_list_); // 刷新最近播放页面
}

/**
 * @brief 处理音乐元数据
 *
 * 该函数负责处理当前播放音乐的元数据，包括音乐名称、歌手和封面图片。
 * 它会从播放器中获取元数据，并更新界面显示。如果没有元数据，
 * 则使用音乐列表中的默认信息。
 */
void Music::handleMetaData()
{
    // 检查cur_page_是否为空
    if (!cur_page_)
    {
        Debug << "当前页面为空，无法处理元数据";
        return;
    }

    // 获取音乐ID并检查索引是否有效
    const QString music_id = cur_page_->getMusicIdByIndex(current_index_);
    if (music_id.isEmpty())
    {
        Debug << "音乐ID无效，无法处理元数据";
        return;
    }

    // 使用music_id在音乐列表中查找对应的音乐项
    const auto it = music_list_.findMusicByMusicId(music_id);
    // 如果未找到对应音乐，输出调试信息并返回
    if (it == music_list_.end())
    {
        Debug << "未找到对应音乐，无法处理元数据";
        return;
    }

    // 从播放器元数据中获取音乐名称和歌手信息
    auto music_name = player_->metaData().value(QMediaMetaData::Title).toString();
    auto singer = player_->metaData().value(QMediaMetaData::ContributingArtist).toString();

    // 如果元数据中没有相关信息，则使用音乐列表中的默认信息
    if (music_name.isEmpty())
    {
        // 获取当前音乐项的名称和歌手信息
        music_name = it->getMusicName(); // 通过迭代器获取音乐名称
        singer = it->getMusicSinger();   // 通过迭代器获取歌手名称
    }
    // 设置音乐名称标签的文本内容
    ui->musicName->setText(music_name);
    // 设置歌手名称标签的文本内容
    ui->musicSinger->setText(singer);

    // 处理音乐封面图片
    const auto cover_image = player_->metaData().value(QMediaMetaData::ThumbnailImage);
    // 检查封面图片对象是否有效
    if (cover_image.isValid())
    {
        // 将 QVariant 类型的封面图片转换为 QImage 对象
        const QImage image = cover_image.value<QImage>();
        // 调整图片大小以适应musicCover标签，保持宽高比，并使用平滑变换
        auto pixmap = QPixmap::fromImage(image).scaled(ui->musicCover->size(),
                                                       Qt::KeepAspectRatio,       // 保持图片原始宽高比
                                                       Qt::SmoothTransformation); // 使用平滑变换算法，提高缩放后的图片质量
        // 将处理后的图片设置到 musicCover 标签上显示
        ui->musicCover->setPixmap(pixmap);
        // 同时将当前页面的音乐图片设置为该 pixmap
        cur_page_->setMusicImage(pixmap);
    }
    else
    {
        // 输出调试信息，提示歌曲没有封面图
        Debug << "歌曲没有封面图";
        // 定义默认封面图的资源路径
        const QString path = ":/images/rec/001.png";
        // 同样调整默认图片大小，保持宽高比，并使用平滑变换以获得更好地缩放效果
        const QPixmap pixmap = QPixmap(path).scaled(ui->musicCover->size(),
                                                    Qt::KeepAspectRatio,
                                                    Qt::SmoothTransformation);
        // 将处理后的图片设置到音乐封面控件上
        ui->musicCover->setPixmap(pixmap);
        // 同时更新当前页面的音乐图片
        cur_page_->setMusicImage(pixmap);
    }

    // 检查迭代器是否有效，如果有效则解析对应的歌词文件
    if (it != music_list_.end())
        lrc_page_->parseLrc(it->getLrcFilePath());
}
/**
 * @brief 皮肤按钮点击事件处理函数
 * @details 当用户点击皮肤按钮时，该函数会被调用，目前仅显示一个提示信息
 *          "加紧支持中"，表示该功能正在开发中
 * @note 该函数是槽函数，通过信号槽机制被调用
 */
void Music::on_skin_clicked()
{
    // 使用QMessageBox显示一个信息提示对话框
    QMessageBox::information(this, "温馨提示", "加紧支持中");
}
/**
 * @brief 最大化按钮点击事件处理函数
 * @details 当用户点击最大化按钮时，该函数会被调用，将主窗口最大化显示
 * @note 该函数是槽函数，通过信号槽机制被调用
 */
void Music::on_max_clicked()
{
    showMaximized();
}
/**
 * @brief 最小化按钮点击事件处理函数
 * @details 当用户点击最小化按钮时，该函数会被调用，将主窗口最小化显示
 * @note 该函数是槽函数，通过信号槽机制被调用
 */
void Music::on_min_clicked()
{
    showMinimized();
}

// 虚函数重写
/**
 * @brief 处理鼠标按下事件，用于窗口拖动
 *
 * @param event 鼠标事件对象
 *
 * 该函数用于处理鼠标按下事件，主要功能是在点击head区域时允许拖动窗口。
 *
 * 具体步骤如下：
 * 1. 检查鼠标按下的按钮是否为左键。
 * 2. 判断鼠标点击位置是否在head区域内。
 * 3. 如果在head区域内，记录鼠标点击位置与窗口左上角的偏移，并设置拖动标志。
 * 4. 如果不在head区域内或不是左键，禁止拖动，并将事件交给父类处理。
 */
void Music::mousePressEvent(QMouseEvent *event)
{
    // 只允许在head区域拖动窗口
    if (event->button() == Qt::LeftButton)
    {
        // 判断鼠标是否点击在head区域
        if (ui->head->geometry().contains(event->pos()))
        {
            // 记录鼠标点击位置与窗口左上角的偏移
            dragPosition = event->globalPosition().toPoint() - frameGeometry().topLeft();
            dragging = true; // 开始拖动
            event->accept(); // 事件已处理
            return;
        }
    }
    dragging = false;                // 不在head区域或不是左键，禁止拖动
    QWidget::mousePressEvent(event); // 交给父类处理其他情况
}
/**
 * @brief 处理鼠标移动事件。
 *
 * 该函数用于处理鼠标在窗口上移动的事件。如果当前正在拖动窗口且鼠标左键按下，
 * 则窗口会根据鼠标的当前位置和拖动起始位置之间的偏移量移动到新的位置。
 * 否则，将事件传递给父类处理。
 *
 * @param event 指向QMouseEvent对象的指针，包含鼠标事件的相关信息。
 */
void Music::mouseMoveEvent(QMouseEvent *event)
{
    // 如果正在拖动且鼠标左键按下
    if (dragging && (event->buttons() & Qt::LeftButton))
    {
        // 移动窗口到新的位置
        move(event->globalPosition().toPoint() - dragPosition);
        return;
    }
    QWidget::mouseMoveEvent(event); // 交给父类处理其他情况
}
/**
 * @brief 处理鼠标释放事件
 *
 * 当鼠标释放时，结束拖动状态，并将事件传递给父类处理其他情况。
 *
 * @param event 指向QMouseEvent对象的指针，包含鼠标事件的相关信息
 */
void Music::mouseReleaseEvent(QMouseEvent *event)
{
    // 鼠标释放时，结束拖动
    dragging = false;
    QWidget::mouseReleaseEvent(event); // 交给父类处理其他情况
}